package org.voovan.korla.rpc;

import org.voovan.korla.exception.KorlaException;
import org.voovan.korla.rpc.annotation.Rpc;
import org.voovan.korla.rpc.annotation.SelectorParam;
import org.voovan.tools.TObject;
import org.voovan.tools.TString;
import org.voovan.tools.compiler.function.DynamicFunction;
import org.voovan.tools.log.Logger;
import org.voovan.tools.reflect.ClassModel;
import org.voovan.tools.reflect.GenericInfo;
import org.voovan.tools.reflect.TReflect;
import org.voovan.tools.serialize.TSerialize;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

/**
 * Rpc 方法信息
 *
 * @author: helyho
 * korla Framework.
 * WebSite: https://github.com/helyho/korla
 * Licence: Apache v2 License
 */
public class RpcMethod {
    private String name;
    private String className;
    private String methodName;
    private String[] paramTypes;
    private String returnType;
    private String[] exceptionTypes;
    private Map<String, String> classModel = new HashMap<String, String>();
    private int selectorParameterIndex = -1;
    private String selectorParamClass;


    private transient Class clazz;
    private transient Method method;

    private boolean singleton = false;
    private boolean idempotence = false;
    private boolean forceFlush = false;

    private transient DynamicFunction methodDynamicFunction;

    public RpcMethod(String className, String methodName, boolean singleton, boolean idempotence, boolean forceFlush, Method method) {
        this.singleton = singleton;
        this.idempotence = idempotence;
        this.forceFlush = forceFlush;

        //方法级的 @SelectorParam 注解处理
        SelectorParam methodSelectorParam = method.getAnnotation(SelectorParam.class);
        if(methodSelectorParam!=null) {
            this.selectorParamClass = methodSelectorParam.value().getCanonicalName();
            selectorParameterIndex = 0;
        }

        this.clazz = method.getDeclaringClass();
        this.className = TString.isNullOrEmpty(className) ? this.clazz.getSimpleName() : className;
        this.method = method;
        this.methodName = TString.isNullOrEmpty(methodName) ? method.getName() : methodName;

        //1.请求参数转换为代码中的定义
        {
            Parameter[] parameters = method.getParameters();

            //如果没有方法级的 @SelectorParam 注解则处理参数级的 @SelectorParam 注解
            if(methodSelectorParam==null) {
                for (int i = 0; i < method.getParameters().length; i++) {
                    if (parameters[i].getAnnotation(SelectorParam.class) != null) {
                        selectorParameterIndex = i;
                    }
                }
            }

            //为方法参数列表增加第一个参数, 并作为 node 节点选择参数
            Type[] methodParamTypes = method.getGenericParameterTypes();
            if(selectorParamClass!=null) {
                //为方法增加第一个参数(@SelectorParam 注解中描述的类型)
                Type[] newParam = new Type[methodParamTypes.length + 1];
                System.arraycopy(new Type[]{methodSelectorParam.value()}, 0, newParam, 0, 1);
                System.arraycopy(methodParamTypes, 0, newParam, 1, methodParamTypes.length);
                methodParamTypes = newParam;
            }

            this.paramTypes = new String[methodParamTypes.length];
            for (int i = 0; i < methodParamTypes.length; i++) {

                GenericInfo genericInfo = TReflect.getGenericInfo(methodParamTypes[i]);
                Class paramClass = genericInfo.getClazz();

                if (ClassModel.hasModel(paramClass)) {
                    ClassModel.getClazzModel(paramClass);
                    paramTypes[i] = genDeclare(genericInfo);

                    TSerialize.register(paramClass);
                    classModel.put(paramClass.getCanonicalName(), ClassModel.getClazzModel(paramClass));
                } else if (genericInfo.getGenericClass().length > 0) {
                    paramTypes[i] = genDeclare(genericInfo);

                    for (Class genericClass : genericInfo.getGenericClass()) {
                        if (ClassModel.hasModel(genericClass)) {
                            TSerialize.register(genericClass);
                            classModel.put(genericClass.getCanonicalName(), ClassModel.getClazzModel(genericClass));
                        }
                    }
                } else {
                    paramTypes[i] = TReflect.getClassName(paramClass);
                    paramTypes[i] = packageFilter(paramTypes[i]);
                }
            }
        }

        //2.方法异常转换为代码中的定义
        Class[] methodExceptionTypes = method.getExceptionTypes();
        exceptionTypes = new String[methodExceptionTypes.length];
        for (int i = 0; i < methodExceptionTypes.length; i++) {
            exceptionTypes[i] = TReflect.getClassName(methodExceptionTypes[i]);
        }

        //3.响应类型转换为代码中的定义
        {
            GenericInfo genericInfo = TReflect.getGenericInfo(method.getGenericReturnType());
            Class retClass = genericInfo.getClazz();
            if (ClassModel.hasModel(retClass)) {
                this.returnType = genDeclare(genericInfo);
                TSerialize.register(method.getReturnType());
                classModel.put(retClass.getCanonicalName(), ClassModel.getClazzModel(method.getReturnType()));
            } else if (genericInfo.getGenericClass().length > 0) {
                this.returnType = genDeclare(genericInfo);

                for (Class genericClass : genericInfo.getGenericClass()) {
                    if (ClassModel.hasModel(genericClass)) {
                        TSerialize.register(genericClass);
                        classModel.put(genericClass.getCanonicalName(), ClassModel.getClazzModel(genericClass));
                    }
                }
            } else {
                this.returnType = TReflect.getClassName(method.getReturnType());
                this.returnType = packageFilter(this.returnType);
            }
        }

        this.name = TObject.nullDefault(name, this.className + "/" + this.methodName);

        methodDynamicFunction = TReflect.getMethodInvoker(clazz, method);
    }

    public static String genDeclare(GenericInfo genericInfo) {
        Class clazz = genericInfo.getClazz();
        String declare = clazz.getSimpleName();

        if(genericInfo.getGenericClass().length !=0) {
            declare = declare + "<";
            for(Class genericClass : genericInfo.getGenericClass()) {
                declare = declare + genericClass.getSimpleName() + ",";
            }

            declare = TString.removeSuffix(declare) + ">";
        }

        declare = packageFilter(declare);
        return declare;
    }

    public static String packageFilter(String className) {
        return className.replace("java.lang.", "").replace("java.util.", "").replace("java.math.", "");
    }

    public String getName() {
        return name;
    }

    public String getClassName() {
        return className;
    }

    public String getMethodName() {
        return methodName;
    }

    public boolean isSingleton() {
        return singleton;
    }

    public boolean isIdempotence() {
        return idempotence;
    }

    public boolean isForceFlush() {
        return forceFlush;
    }

    public void setForceFlush(boolean forceFlush) {
        this.forceFlush = forceFlush;
    }

    public Class getClazz() {
        return clazz;
    }

    public Method getMethod() {
        return method;
    }

    public String[] getParamTypes() {
        return paramTypes;
    }

    public String getReturnType() {
        return returnType;
    }

    public String[] getExceptionTypes() {
        return exceptionTypes;
    }

    public Map<String, String> getClassModel() {
        return classModel;
    }

    public Object invoke(Object ... params) throws Exception {
        Object invokeObj = null;

        if (singleton) {
            invokeObj = RpcStatic.RPC_SINGLETON_OBJECTS.get(clazz);
        } else {
            invokeObj = TReflect.newInstance(clazz);
        }

        Type[] genericTypes = method.getGenericParameterTypes();

        if(params.length != genericTypes.length) {
            throw new IllegalArgumentException("Method " + method + " except: " + genericTypes.length + ", params: " + params.length);
        }

        for(int i=0;i<params.length; i++) {
            Type paramType = genericTypes[i];
            params[i] = TObject.convert(params[i], paramType);
        }

//        Object response = TReflect.invokeMethod(invokeObj, method.getName(), params);
        Object response = methodDynamicFunction.call(invokeObj, params);
        return response;
    }

    /**
     * 用于生成 Rpc 调用当前方法的代码
     * @param isCallback 是否生成基于回调的代码
     * @return 方法的调用代码
     */
    public String buildMethodCode(boolean isCallback) {

        StringBuffer methodCodeBuffer = new StringBuffer("public ");

        String methodName = getMethodName();

        //方法返回值及名称
        methodCodeBuffer.append((isCallback ? "void" : returnType) + " " + methodName + "(");

        //方法参数
        for(int i =0;i<paramTypes.length;i++ ) {
            methodCodeBuffer.append(paramTypes[i] + " " + "var" + i + ",");
        }

        if(isCallback) {
            methodCodeBuffer.append(" RpcCallback<" + (returnType.equals("void") ? "Void" : returnType) + ">" + "callback");
        } else if(methodCodeBuffer.charAt(methodCodeBuffer.length()-1)==',') {
            methodCodeBuffer.setLength(methodCodeBuffer.length()-1);
        }

        methodCodeBuffer.append(")");

        //方法抛出的异常部分
        if(!isCallback) {
            methodCodeBuffer.append(" throws TimeoutException, RpcCallbackException");
        }

        //方法体
        methodCodeBuffer.append(" {");


        //line 1 获取 node
        if(selectorParameterIndex>=0){
            methodCodeBuffer.append("String node = selector().apply((K)var" + selectorParameterIndex + ", this);");
        } else {
            methodCodeBuffer.append("String node = selector().apply(selectorNode.get(), this);");
        }
        //line 2 构造 RpcCall 对象
        methodCodeBuffer.append("RpcCall rpcCall = RpcCall.newInstance(\"" + name + "\",");
        //Rpc 参数
        int paramStartIndex = this.selectorParamClass == null ? 0 : 1;
        for(int i =paramStartIndex;i<paramTypes.length;i++ ) {
            methodCodeBuffer.append(" " + "var" + i + ",");
        }

        methodCodeBuffer.setLength(methodCodeBuffer.length()-1);

        methodCodeBuffer.append(");");

        //line 3 设置分片 id
        if(selectorParameterIndex>=0){
            methodCodeBuffer.append("rpcCall.setSliceKey(var" + selectorParameterIndex + ");");
        }

        //line 4 设置节点 id
        methodCodeBuffer.append("rpcCall.setNode(node);");

        //line 5 使用 RpcConsumer.call 调用远端 Rpc 方法
        if(!returnType.equals("void") && !isCallback) {
            methodCodeBuffer.append("return ");
        }

        if(!isCallback) {
            methodCodeBuffer.append("getByName(node).call(rpcCall);");
        } else {
            methodCodeBuffer.append("getByName(node).call(rpcCall, callback);");
        }

        methodCodeBuffer.append("}");

        return methodCodeBuffer.toString();
    }


    public static RpcMethod newInstance(String className, Rpc methodAnnotation, Method method) {
        String methodName = methodAnnotation.value();
        boolean singleton = methodAnnotation.singleton();
        boolean idempotence = methodAnnotation.idempotence();
        boolean forceFlush = methodAnnotation.forceFlush();
        return new RpcMethod(className, methodName, singleton, idempotence, forceFlush, method);
    }
}
